package com.xiaobawang.knowledge.spider.util;


import com.xiaobawang.knowledge.spider.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;

import java.text.SimpleDateFormat;
import java.text.DateFormat;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.text.ParseException;
import java.util.*;
import java.time.DayOfWeek;
import java.util.concurrent.TimeUnit;

/**
 * @author synhard
 * @description 日期处理
 * @date 2024/10/16 14:50
 * @email songxinhang2023@e-chinalife.com
 * @id 18101119
 * @phone 13001321080
 */
@Slf4j
public class DateUtil {
    private DateUtil() {
        throw new IllegalStateException("Utility class");
    }

    // ---------------------- format parse ----------------------
    public static final String DATE_FORMAT = "yyyy-MM-dd";
    public static final String DATE_FORMAT_SLASH = "yyyy/MM/dd";
    public static final String DATE_FORMAT_SLASH_SINGLE = "yyyy/M/d";
    public static final String DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DATE_FORMAT_CHINESE = "yyyy年MM月dd日";
    public static final String DATETIME_FORMAT_NO_SECOND = "yyyy-MM-dd HH:mm";
    public static final String DATETIME_FORMAT_CHINESE = "yyyy年MM月dd日 HH:mm:ss";
    public static final String DATETIME_PURE = "yyyyMMddHHmmss";
    public static final String TIME_FORMAT_NO_SECOND = "HH:mm:ss";
    public static final String YEAR = "yyyy";
    public static final String DATE_FORMAT_NO_YEAR = "MM月dd日";
    public static final String DATETIME_LINE = "yyyy-MM-dd-HH:mm:ss";

    public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    private static final ThreadLocal<Map<String, DateFormat>> dateFormatThreadLocal = new ThreadLocal<>();

    private static DateFormat getDateFormat(String pattern) {
        if (pattern == null || pattern.trim().isEmpty()) {
            throw new IllegalArgumentException("pattern cannot be empty.");
        }

        Map<String, DateFormat> dateFormatMap = dateFormatThreadLocal.get();
        if (dateFormatMap != null && dateFormatMap.containsKey(pattern)) {
            return dateFormatMap.get(pattern);
        }

        synchronized (dateFormatThreadLocal) {
            if (dateFormatMap == null) {
                dateFormatMap = new HashMap<String, DateFormat>();
            }
            dateFormatMap.put(pattern, new SimpleDateFormat(pattern));
            dateFormatThreadLocal.set(dateFormatMap);
        }

        return dateFormatMap.get(pattern);
    }

    /**
     * format datetime. like "yyyy-MM-dd"
     *
     * @param date
     * @return
     * @throws ParseException
     */
    public static String formatDate(Date date, String format) {
        return format(date, format == null ? DATE_FORMAT : format);
    }

    /**
     * format date. like "yyyy-MM-dd HH:mm:ss"
     *
     * @param date
     * @return
     * @throws ParseException
     */
    public static String formatDateTime(Date date) {
        return format(date, DATETIME_FORMAT);
    }

    /**
     * format date
     *
     * @param date
     * @param patten
     * @return
     * @throws ParseException
     */
    public static String format(Date date, String patten) {
        if (date == null) {
            return "";
        }
        return getDateFormat(patten).format(date);
    }

    /**
     * parse date string, like "yyyy-MM-dd"
     *
     * @param dateString
     * @return
     * @throws ParseException
     */
    public static Date parseDate(String dateString, String format) {
        return parse(dateString, format == null ? DATE_FORMAT : format);
    }

    /**
     * parse datetime string, like "yyyy-MM-dd HH:mm:ss"
     *
     * @param dateString
     * @return
     * @throws ParseException
     */
    public static Date parseDateTime(String dateString, String dateFormat) {
        if (dateFormat == null) {
            return parse(dateString, DATETIME_FORMAT);
        }
        return parse(dateString, dateFormat);
    }

    /**
     * String 转 LocalDate
     * @param date
     * @return
     */
    public static LocalDate string2LocalDate(String date){
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
        return LocalDate.parse(date, formatter);
    }

    /**
     * String yyyy-MM-dd 转 String MM月dd日
     * @return
     */
    public static String stringDateModify(String formerDate){
        LocalDate startLocalDate = DateUtil.string2LocalDate(formerDate);
        return startLocalDate.format(DateTimeFormatter.ofPattern(DATE_FORMAT_NO_YEAR));
    }

    /**
     * @author 18101119
     * @description 根据日期字符串获取周几
     * @date 2024/10/22 10:50
     * @param dateStr 日期字符串
     * @return java.lang.String
     */
    public static String getWeekday(String dateStr) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
        try {
            LocalDate date = LocalDate.parse(dateStr, formatter);
            DayOfWeek dayOfWeek = date.getDayOfWeek();
            return convertToChinese(dayOfWeek);
        } catch (Exception e) {
            throw  new BusinessException("Invalid date format or value");
        }
    }

    /**
     * 判断两个日期之间的差异是否超过了指定的分钟数
     * @param date1 第一个日期
     * @param date2 第二个日期
     * @param minutes 指定的分钟数
     * @return 如果两个日期之间的差异超过了指定的分钟数，则返回 true；否则返回 false
     */
    public static boolean isDifferenceExceedsMinutes(Date date1, Date date2, int minutes) {
        long diffInMillis = Math.abs(date1.getTime() - date2.getTime()); // 计算两个日期之间的毫秒数差异
        long diffInMinutes = TimeUnit.MILLISECONDS.toMinutes(diffInMillis); // 将毫秒转换成分钟
        return diffInMinutes > minutes;
    }

    private static String convertToChinese(DayOfWeek dayOfWeek) {
        switch (dayOfWeek) {
            case MONDAY:
                return "周一";
            case TUESDAY:
                return "周二";
            case WEDNESDAY:
                return "周三";
            case THURSDAY:
                return "周四";
            case FRIDAY:
                return "周五";
            case SATURDAY:
                return "周六";
            case SUNDAY:
                return "周日";
            default:
                return "未知";
        }
    }

    /**
     * parse date
     *
     * @param dateString
     * @param pattern
     * @return
     * @throws ParseException
     */
    public static Date parse(String dateString, String pattern) {
        try {
            Date date = getDateFormat(pattern).parse(dateString);
            return date;
        } catch (Exception e) {
            log.warn("parse date error, dateString = {}, pattern={}; errorMsg = {}", dateString, pattern, e.getMessage());
            return null;
        }
    }

    /**
     * 获取指定日期的字符串表示。
     * @param year 年份
     * @param month 月份（0表示不指定月份）
     * @param day 天数（0表示不指定天数）
     * @return 对应的日期字符串，格式为 yyyy-MM-dd
     * @throws IllegalArgumentException 如果输入日期不合法
     */
    public static String getDateStr(int year, int month, int day) {
        if (year == 0) {
            throw new IllegalArgumentException("Year cannot be 0.");
        }

        if (month == 0 && day == 0) {
            return getFirstAndLastDayOfYear(year);
        } else if (day == 0) {
            return getFirstAndLastDayOfMonth(year, month);
        } else {
            return getSpecificDate(year, month, day);
        }
    }

    /**
     * @author 18101119
     * @description 判断日期字符串是否符合格式
     * @date 2024/10/22 10:05
     * @param dateStr 日期字符串
     * @return boolean
     */
    public static boolean isDateValid(String dateStr) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
        try {
            LocalDate.parse(dateStr, formatter);
            return true;
        } catch (DateTimeParseException e) {
            return false;
        }
    }

    /**
     * 获取指定年份的第一天和最后一天日期字符串。
     * @param year 年份
     * @return 第一天和最后一天日期字符串，格式为 "yyyy-MM-dd, yyyy-MM-dd"
     */
    private static String getFirstAndLastDayOfYear(int year) {
        LocalDate firstDay = LocalDate.of(year, 1, 1);
        LocalDate lastDay = LocalDate.of(year, 12, 31);
        return firstDay.format(DATE_FORMATTER) + ", " + lastDay.format(DATE_FORMATTER);
    }

    /**
     * 获取指定月份的第一天和最后一天日期字符串。
     * @param year 年份
     * @param month 月份
     * @return 第一天和最后一天日期字符串，格式为 "yyyy-MM-dd, yyyy-MM-dd"
     */
    private static String getFirstAndLastDayOfMonth(int year, int month) {
        YearMonth yearMonth = YearMonth.of(year, month);
        LocalDate firstDay = yearMonth.atDay(1);
        LocalDate lastDay = yearMonth.atEndOfMonth();
        return firstDay.format(DATE_FORMATTER) + ", " + lastDay.format(DATE_FORMATTER);
    }

    /**
     * 获取指定日期的字符串表示。
     * @param year 年份
     * @param month 月份
     * @param day 天数
     * @return 对应的日期字符串，格式为 yyyy-MM-dd
     * @throws IllegalArgumentException 如果输入日期不合法
     */
    private static String getSpecificDate(int year, int month, int day) {
        try {
            LocalDate date = LocalDate.of(year, month, day);
            return date.format(DATE_FORMATTER);
        } catch (DateTimeParseException e) {
            throw new BusinessException("Invalid date: " + year + "-" + month + "-" + day);
        }
    }

    /**
     * @author 18101119
     * @description 根据日期返回当天所有日期
     * @date 2025/2/17 10:38
     * @param date 日期
     * @return java.util.List<java.lang.String>
     */
    public static List<String> getWeekDates(String date, String format) {
        List<String> weekDates = new ArrayList<>();
        SimpleDateFormat sdf;
        if (format == null) {
            sdf = new SimpleDateFormat(DateUtil.DATE_FORMAT);
        } else {
            sdf = new SimpleDateFormat(format);
        }
        Calendar calendar = Calendar.getInstance();

        try {
            // 设置日历为传入的日期
            calendar.setTime(sdf.parse(date));

            // 获取当前日期是本周的第几天（周日是1，周一是2，依此类推）
            int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);

            // 调整到本周的第一天（周日）
            calendar.add(Calendar.DATE, - (dayOfWeek - 1));

            // 添加本周的所有日期
            for (int i = 0; i < 7; i++) {
                weekDates.add(sdf.format(calendar.getTime()));
                calendar.add(Calendar.DATE, 1); // 下一天
            }
        } catch (Exception e) {
            throw new BusinessException("getWeekDates error: " + e.getMessage());
        }
        return weekDates;
    }

    /**
     * 根据开始日期和结束日期，返回包括这两个日期在内的所有日期
     *
     * @param startDate 开始日期
     * @param endDate   结束日期
     * @return 日期范围内的所有日期
     */
    public static List<String> getDateRange(String startDate, String endDate, String format) {
        List<String> dateRange = new ArrayList<>();
        SimpleDateFormat sdf;
        if (format == null) {
            sdf = new SimpleDateFormat(DateUtil.DATE_FORMAT);
        } else {
            sdf = new SimpleDateFormat(format);
        }
        Calendar calendar = Calendar.getInstance();

        try {
            // 解析开始日期和结束日期
            Date start = sdf.parse(startDate);
            Date end = sdf.parse(endDate);

            // 设置日历为开始日期
            calendar.setTime(start);

            // 循环添加日期，直到超过结束日期
            while (!calendar.getTime().after(end)) {
                dateRange.add(sdf.format(calendar.getTime()));
                calendar.add(Calendar.DATE, 1); // 下一天
            }
        } catch (Exception e) {
            throw new BusinessException("getDateRange error:" + e.getMessage());
        }

        return dateRange;
    }

    /**
     * 传入一个只有年月日的日期字符串以及这个字符串的格式，生成目标格式的日期字符串
     *
     * @param dateString 日期字符串（例如 "2023-10-15"）
     * @param sourceFormat 源日期格式（例如 "yyyy-MM-dd"）
     * @param targetFormat 目标日期格式（例如 "dd/MM/yyyy"）
     * @return 目标格式的日期字符串
     */
    public static String formatDateString(String dateString, String sourceFormat, String targetFormat) {
        try {
            SimpleDateFormat sourceFormatter = new SimpleDateFormat(sourceFormat);
            SimpleDateFormat targetFormatter;

            if (targetFormat == null) {
                targetFormatter = new SimpleDateFormat(DATE_FORMAT);
            } else {
                targetFormatter = new SimpleDateFormat(targetFormat);
            }

            // 解析日期字符串为 Date 对象
            Date date = sourceFormatter.parse(dateString);

            // 格式化为目标格式的日期字符串
            return targetFormatter.format(date);
        } catch (Exception e) {
            throw new BusinessException(e.getMessage());
        }
    }

    /**
     * @author 18101119
     * @description 获取星期数
     * @date 2025/2/18 8:55
     * @param dateString 日期字符串
     * @param formatString 日期格式字符串
     * @return int
     */
    public static int getWeekdayFromDateString(String dateString, String formatString) {
        SimpleDateFormat sdf;
        if (formatString == null) {
            sdf = new SimpleDateFormat(DATE_FORMAT);
        } else {
            sdf = new SimpleDateFormat(formatString);
        }
        try {
            Date date = sdf.parse(dateString);
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(date);

            // 获取星期几，Calendar.SUNDAY 是 1, Calendar.MONDAY 是 2, ..., Calendar.SATURDAY 是 7
            int weekday = calendar.get(Calendar.DAY_OF_WEEK);

            // 转换为星期一返回1，星期日返回7
            if (weekday == Calendar.SUNDAY) {
                return 7;
            } else {
                return weekday - 1;
            }
        } catch (ParseException e) {
            e.printStackTrace();
            return -1;
        }
    }

    // ---------------------- add date ----------------------

    public static Date addYears(final Date date, final int amount) {
        return add(date, Calendar.YEAR, amount);
    }

    public static Date addMonths(final Date date, final int amount) {
        return add(date, Calendar.MONTH, amount);
    }

    public static Date addDays(final Date date, final int amount) {
        return add(date, Calendar.DAY_OF_MONTH, amount);
    }

    public static Date addHours(final Date date, final int amount) {
        return add(date, Calendar.HOUR_OF_DAY, amount);
    }

    public static Date addMinutes(final Date date, final int amount) {
        return add(date, Calendar.MINUTE, amount);
    }

    public static Date addSeconds(final Date date, final int amount) {
        return add(date, Calendar.SECOND, amount);
    }

    private static Date add(final Date date, final int calendarField, final int amount) {
        if (date == null) {
            return null;
        }
        final Calendar c = Calendar.getInstance();
        c.setTime(date);
        c.add(calendarField, amount);
        return c.getTime();
    }

}